
package system.communication;

import java.util.LinkedList;
import system.track.Direction;
import system.track.SwitchAngle;
import system.train.HandControlTrain;

/**
 * The HandController retains the status of the physical HandController. It is
 * used to get current Direction, Halted, Connected, SwitchAngle, and Speed status's
 * of the physical HandController. It can also determine changes in individual status's
 * of the afformentioned.
 *
 * @author elliot
 */
public class HandController
{
    private LinkedList<HandControllerStatus> statusQueue;
    private int id;
    private HandControlTrain train;
    private boolean crashFlip;
    private LinkedList<HandControllerStatus> crashFlipQueue;

    /**
     * Overloaded HandController constructor.
     *
     * @param id int which sets the integer id of a physical HandController to this
     * virtual representation of the HandController.
     */
    public HandController(int id)
    {
        statusQueue = new LinkedList<HandControllerStatus>();
        this.id = id;
        crashFlip = false;
        crashFlipQueue = new LinkedList<HandControllerStatus>();
    }

    public void initCrashFlip() {

        //Turn off statusUpdate calls to the train.
        crashFlip = true;

        //Clear the status queue.
        crashFlipQueue.clear();
        
        //Copy over initial state.
        crashFlipQueue.addFirst(statusQueue.getFirst());
    }

    /**
     * Registers a Train object to the HandController.
     *
     * @param t HandControlTrain object to register.
     */
    public synchronized void registerTrain(HandControlTrain t)
    {
        this.train = t;
    }

    /**
     * Updates the current status of the HandController. Throws out a message if
     * the current status equals the status of the incoming message. After a status
     * is successfully added the train is updated to the HandControllers status.
     *
     * @param message Message containing HandControl MessageArgs.
     */
    public void updateStatus(Message message)
    {
        HandControllerStatus tempStatus = new HandControllerStatus(message);
        if (!crashFlip)
        {
            if (checkAndAppend(statusQueue, tempStatus))
            {
                try
                {
                    train.statusUpdate();
                }
                catch (NullPointerException ex)
                {
                    //Do nothing.  We would like to try to get to the train,
                    //but if we have no train, it's fine
                }
            }

        }
        else
        {
            checkAndAppend(crashFlipQueue, tempStatus);
        }
    }

    /**
     * Retrieves the current Direction set by the physical HandController.
     *
     * @return Direction the current Direction of the HandController.
     */
    public Direction getCurrentDirection()
    {
        return statusQueue.getFirst().getDirection();
    }

    /**
     * Retreives the current SwitchAngle set by the physical HandController.
     *
     * @return SwitchAngle the current SwitchAngle of the HandController.
     */
    public SwitchAngle getCurrentSwitchAngle()
    {
        return statusQueue.getFirst().getSwitchAngle();
    }

    /**
     * Retrieves the current Halted status set by the physical HandController.
     *
     * @return boolean a boolean value representing the current status of the
     * HandController. True for Active Halted and False for Inactive Halted.
     */
    public boolean getCurrentHaltedStatus()
    {
        return statusQueue.getFirst().getHaltedStatus();
    }

    /**
     * Retreives the current Connected status set by the physical HandController.
     *
     * @return boolean a boolean value representing the current status of the
     * HandController. True for Active Connected False for Inactive Connected.
     */
    public boolean getCurrentConnectedStatus()
    {
        return statusQueue.getFirst().getConnectedStatus();
    }

    /**
     * Retreives the current Speed set by the physical HandController.
     *
     * @return int an integer value representing the current speed of the
     * HandController.
     */
    public int getCurrentSpeed()
    {
        return statusQueue.getFirst().getSpeed();
    }

    /**
     * Compares the current Direction to the previous Direction and returns a
     * boolean indicating if the directional status has changed.
     *
     * @return boolean a boolean value representing if directional status has
     * changed in the HandController. True for changed, False for unchanged.
     */
    public boolean hasDirectionChanged()
    {
        boolean changed = false;
        if(statusQueue.size() >= 2)
        {
            if(statusQueue.get(0).getDirection() != statusQueue.get(1).getDirection())
            {
                changed = true;
            }
        }
        return changed;
    }

    /**
     * Compares the current Halted status to the previous Halted status and
     * returns a boolean indicating if the Halted status has changed.
     *
     * @return boolean a boolean value representing if Halted status has
     * changed in the HandController. True for changed, False for unchanged.
     */
    public boolean hasHaltedChanged()
    {
        boolean changed = false;
        if(statusQueue.size() >= 2)
        {
            if(statusQueue.get(0).getHaltedStatus() != statusQueue.get(1).getHaltedStatus())
            {
                changed = true;
            }
        }
        return changed;
    }

    /**
     * Compares the current speed to the previous speed and returns a
     * boolean indicating if the speed has changed.
     *
     * @return boolean a boolean value representing if speed has changed
     * in the HandController. True for changed, False for unchanged.
     */
    public boolean hasSpeedChanged()
    {
        boolean changed = false;
        if(statusQueue.size() >= 2)
        {
            if(statusQueue.get(0).getSpeed() != statusQueue.get(1).getSpeed())
            {
                changed = true;
            }
        }
        return changed;
    }

    /**
     * Compares the current Connected status to the previous Connected status and
     * returns a boolean indicating if the Connected status has changed.
     *
     * @return boolean a boolean value representing if Connected status has
     * changed in the HandController. True for changed, False for unchanged.
     */
    public boolean hasConnectedChanged()
    {
        boolean changed = false;
        if(statusQueue.size() >= 2)
        {
            if(statusQueue.get(0).getConnectedStatus() != statusQueue.get(1).getConnectedStatus())
            {
                changed = true;
            }
        }
        return changed;
    }

    /**
     * Compares the current SwitchAngle status to the previous SwitchAngle status and
     * returns a boolean indicating if the SwitchAngle status has changed.
     *
     * @return boolean a boolean value representing if SwitchAngle status has
     * changed in the HandController. True for changed, False for unchanged.
     */
    public boolean hasSwitchAngleChanged()
    {
        boolean changed = false;
        if(statusQueue.size() >= 2)
        {
            if(statusQueue.get(0).getSwitchAngle() != statusQueue.get(1).getSwitchAngle())
            {
                changed = true;
            }
        }
        return changed;
    }

    /**
     * Indicates if the Halted Switch has been flipped back and forth from
     * Inactive Halted to Active Halted to Inactive Halted and returns a boolean
     * value representing this change
     *
     * @return boolean a boolean value representing if Halted switch has been
     * flipped back and forth. True for has been flipped, False otherwise.
     */
    public boolean haltSwitchFlipped()
    {
        boolean flipped = false;
        if(crashFlipQueue.size() >= 3 && crashFlip)
        {
            //This condition tests for HALT/HALT/NOT HALT.
            //We only use this set of states because we want to
            //end on a NOT HALT.
            if(  ( crashFlipQueue.get(0).getHaltedStatus()  &&
                   !crashFlipQueue.get(1).getHaltedStatus()  &&
                   crashFlipQueue.get(2).getHaltedStatus())
               )
            { //We've flipped a switch.  Load the previous state into the
              //status queue for use in uncrashing.
                flipped = true;
                crashFlip = false;
                statusQueue.addFirst(crashFlipQueue.get(0));
            }
        }
        return flipped;
    }

    /**
     * Attempts to add a HandControllerStatus to a queue, but will only do
     * so if the status object is not equal to the last known status.
     *
     * If the queue is empty, the status is added unconditionally.
     * 
     * @param hcsQueue the queue to attempt to add the new status to.
     * @param tempStatus the new status
     * @return <code>true</code> if the status was different and added.
     */
    private boolean checkAndAppend(LinkedList<HandControllerStatus> hcsQueue, HandControllerStatus tempStatus)
    {
        boolean added = false;
        if (hcsQueue.size() == 0)
        {
            hcsQueue.add(tempStatus);
            added = true;
        }
        if (!hcsQueue.getFirst().equals(tempStatus))
        {
            hcsQueue.addFirst(tempStatus);
            added = true;
            if (hcsQueue.size() == 4)
            {
                hcsQueue.removeLast();
            }

        }
        return added;
    }

    /**
     * A private internal class in the HandController representing a single status
     * of the HandController.
     */
    private class HandControllerStatus
    {
        private Direction direction;
        private SwitchAngle switchAngle;
        private boolean halted, connected;
        private int speed;

        /**
         * Overloaded constructor for the HandControllerStatus. Parses a message
         * and compares its arguments and sets the local variables to their
         * respective values.
         *
         * @param message A Message object with HandControl MessageArgs.
         */
        public HandControllerStatus(Message message)
        {
            //set Direction Status
            if(message.getArg(MessageArg.HANDCONTROL_BLOCK_DIRECTION).equals("CW"))
            {
                direction = Direction.CLOCKWISE;
            }
            else
            {
                direction = Direction.COUNTERCLOCKWISE;
            }
            //set switchAngle status
            if(message.getArg(MessageArg.HANDCONTROL_SWITCH_ANGLE).equals("90"))
            {
                switchAngle = SwitchAngle.NINETY_DEGREES;
            }
            else
            {
                switchAngle = SwitchAngle.ONE_EIGHTY_DEGREES;
            }
            //set haltedStatus
            if(message.getArg(MessageArg.HANDCONTROL_HALTED).equals("UH"))
            {
                halted = false;
            }
            else
            {
                halted = true;
            }
            //set connectedStatus
            if(message.getArg(MessageArg.HANDCONTROL_CONNECTED).equals("UC"))
            {
                connected = false;
            }
            else
            {
                connected = true;
            }
            speed = Integer.parseInt(message.getArg(MessageArg.HANDCONTROL_SPEED));
        }

        /**
         * Retrieves the Direction from the HandControllerStatus
         *
         * @return Direction enum.
         */
        public Direction getDirection()
        {
            return direction;
        }

        /**
         * Retrieves the SwitchAngle from the HandControllerStatus
         *
         * @return SwitchAngle enum.
         */
        public SwitchAngle getSwitchAngle()
        {
            return switchAngle;
        }

        /**
         * Retrieves the speed from the HandControllerStatus
         *
         * @return int representing the speed.
         */
        public int getSpeed()
        {
            return speed;
        }

        /**
         * Retrieves the HaltedStatus from the HandControllerStatus
         *
         * @return boolean representing the Halted Status. True for Inactive
         * Halted, false for Active Halted.
         */
        public boolean getHaltedStatus()
        {
            return halted;
        }

        /**
         * Retrieves the ConnectedStatus from the HandControllerStatus
         *
         * @return boolean representing the Connected Status. True for Active
         * Connected, false for Inactive Connected.
         */
        public boolean getConnectedStatus()
        {
            return connected;
        }

        /**
         * Compares calling HandControllerStatus object with HandControllerStatus
         * parameter for equality.
         *
         * @param hcs HandControllerStatus object.
         * @return boolean value representing equality, True for equal, false
         * otherwise.
         */
        public boolean equals(HandControllerStatus hcs)
        {
            boolean returnVal = false;
            if(direction.equals(hcs.getDirection()))
            {
                if(switchAngle.equals(hcs.getSwitchAngle()))
                {
                    if(speed == hcs.getSpeed())
                    {
                        if(halted == hcs.getHaltedStatus())
                        {
                            if(connected == hcs.getConnectedStatus())
                            {
                                returnVal = true;
                            }
                        }
                    }
                }
            }
            return returnVal;
        }
    }
}
